本文同步更新於blog
情境:以下是某搜尋功能
<?php
namespace App\ProxyPattern\Cache;
use App\ProxyPattern\Cache\Database;
class Program
{
    /**
     * @var Database
     */
    protected $database;
    public function __construct()
    {
        $this->database = new Database();
    }
    /**
     * @param string $keyword
     * @return array
     */
    public function search(string $keyword): array
    {
        return $this->database->read($keyword);
    }
}
<?php
namespace App\ProxyPattern\Cache;
class Database
{
    /**
     * @param string $keyword
     * @return array
     */
    public function read(string $keyword): array
    {
        if ($keyword == 'sushi') {
            return ['Bear Sushi', 'Lin Sushi', 'Alysa Sushi'];
        }
        return [];
    }
}
老闆希望搜尋時,若是已搜尋過的資料,
便由快取返回,不再呼叫實體資料庫。
讓我們用代理模式改造它。
需求一:實現快取代理
<?php
namespace App\ProxyPattern\Cache\Contracts;
interface Readable
{
    /**
     * @param string $keyword
     * @return array
     */
    public function read(string $keyword): array;
}
<?php
namespace App\ProxyPattern\Cache;
use App\ProxyPattern\Cache\Contracts\Readable;
class Database implements Readable
{
    /**
     * @param string $keyword
     * @return array
     */
    public function read(string $keyword): array
    {
        if ($keyword == 'sushi') {
            return ['Bear Sushi', 'Lin Sushi', 'Alysa Sushi'];
        }
        return [];
    }
}
<?php
namespace App\ProxyPattern\Cache;
use App\ProxyPattern\Cache\Contracts\Readable;
class CacheProxy implements Readable
{
    /**
     * @var array
     */
    protected $cached = [];
    /**
     * @var Database
     */
    protected $database;
    public function __construct()
    {
        $this->database = new Database();
    }
    /**
     * @param string $keyword
     * @return array
     */
    public function read(string $keyword): array
    {
        if (isset($this->cached[$keyword])) {
            return $this->cached[$keyword];
        }
        $result = $this->database->read($keyword);
        $this->cached[$keyword] = $result;
        return $result;
    }
}
<?php
namespace App\ProxyPattern\Cache;
use App\ProxyPattern\Cache\CacheProxy;
class Program
{
    /**
     * @var CacheProxy
     */
    protected $proxy;
    public function __construct()
    {
        $this->proxy = new CacheProxy();
    }
    /**
     * @param string $keyword
     * @return array
     */
    public function search(string $keyword): array
    {
        return $this->proxy->read($keyword);
    }
}
這下子客戶端搜尋時,若快取代理有資料,便會直接返回結果。
[單一職責原則]
我們將實體類別與代理類別視作兩種不同的職責。
代理類別主要處理訪問實體類別的行為。
[開放封閉原則]
當我們需要實現不屬於實體類別的職責時(例如:關鍵字被搜尋次數),
我們可以在代理類別中實現,不須修改實體類別的程式碼。
若有需要其他控制訪問的職責時,也可以新增代理類別。
除了上述的提出介面的委派方法外,
也有人用繼承的手法修改行為,但我個人比較不喜歡。
最後附上類別圖:
(註:若不熟悉 UML 類別圖,可參考UML類別圖說明。)
ʕ •ᴥ•ʔ:代理類別就像是實體類別的經紀人一樣。